#include "LegendPlotUtil.h"
#include "MappingUtil.h"

#include "EPlotRenderer.h"
#include "Stylizer.h"
#include "FeatureTypeStyleVisitor.h"

#include "icons.h"


const double M_TO_MI  = 0.0006214;
const double MI_TO_M  = 1609.34721869443738887477;
const double MI_TO_IN = 63360;
const double IN_TO_MI = 0.0000157828283;
const double FT_TO_IN = 12;
const double IN_TO_FT = 0.0833333333;
const double KM_TO_IN = 39370.0787 ;
const double IN_TO_KM = 0.0000254;
const double M_TO_IN  = 39.3700787 ;
const double M_TO_MM  = 1000;
const double MM_TO_IN = 0.0393700787 ;
const double MM_TO_M  = 0.001;
const double IN_TO_M  = 0.0254;
const double IN_TO_CM = 2.54;
const double IN_TO_MM = 25.4;

const double PNT_TO_IN = 0.0138888888889;  // 1 font point =  1/72 of an inch

const double defaultScalebarWidth = 0.66;  // percent of page width
const double minScalebarWidth = 3.0;  // inch
const double maxScalebarWidth = 6.0;  // inch
const double whiteScaleDivisionHeight = 0.0625;  // inch
const double blackScaleDivisionStartY = 0.015625;  //inch
const double blackScaleDivisionEndY = 0.046875;  // inch
const double scaleBarTickMarkHeight = 0.0573;  // inch
const double scaleHeaderOffsetY = 0.188;  // inch
const double scaleHeaderFontHeight = 0.003; // meters
const double scaleLabelFontHeight = 0.002; // meters
const double scaleLabelOffsetY = 0.1406; // inch
const double scaleFooterOffsetY = 0.2344; // inch
const STRING scaleLabelPrefix = L"Scale 1 : ";

const double northArrowWidth = 1.0;   // inch

// Relative offset amounts for vertices in North Arrow graphics.  Units are in inches.
// See MgServerMappingService::AddNorthArrowElement() method below for description
// of the vertices.
const double northArrowMinVertexX = -0.4062;
const double northArrowMidVertexX = -0.25;
const double northArrowMaxVertexX = -0.0937;
const double northArrowMinVertexY =  0.0;
const double northArrowMidVertexY =  0.1563;
const double northArrowMaxVertexY =  0.3776;
const double northArrowNVertexOffsetMinX = -0.2812;
const double northArrowNVertexOffsetMinY =  0.4063;
const double northArrowNVertexOffsetMaxX = -0.2187;
const double northArrowNVertexOffsetMaxY =  0.5;

const INT32 bitmapPixelWidth = 16;
const INT32 bitmapPixelHeight = 16;
const double bitmapDpi = STANDARD_DISPLAY_DPI;

const double legendSpacing = bitmapPixelHeight / bitmapDpi + 0.005;
const double defaultLegendMargin = 0.1; // inch
const double legendFontHeightMeters = 0.002;
const double legendTextVertAdjust = 0.07;  // inch

const double PrintLegendWidth     = 2.0;
const double PrintLegendPadding   = 0.1;
const double PrintHeaderHeight    = 1.0;
const double PrintScalebarHeight  = 0.75;
const double PrintScalebarPadding = 0.5;
const double PrintFooterHeight    = 0.5;
const double PrintGroupIndent     = 0.3;


// Static helper method to draw a PNG icon.  Calling Renderer::ProcessRaster
// renders the PNG upside down.
static void DrawPNG(Renderer* dr, unsigned char* data, int length, int width, int height, RS_Bounds& extents)
{
    SE_Renderer* drSE = dynamic_cast<SE_Renderer*>(dr);
    if (!drSE)
        return;

    double minx, miny, maxx, maxy;
    drSE->WorldToScreenPoint(extents.minx, extents.miny, minx, miny);
    drSE->WorldToScreenPoint(extents.maxx, extents.maxy, maxx, maxy);

    double cx = 0.5 * (minx + maxx);
    double cy = 0.5 * (miny + maxy);
    double imgDevW = fabs(maxx - minx);
    double imgDevH = fabs(maxy - miny);

    drSE->DrawScreenRaster(data, length, RS_ImageFormat_PNG, width, height, cx, cy, imgDevW, imgDevH, 0.0);
}


MgLegendPlotUtil::MgLegendPlotUtil(MgResourceService* svcResource)
{
    m_svcResource = SAFE_ADDREF(svcResource);

    // get the name of the font to use with the legend
    MgConfiguration* pConf = MgConfiguration::GetInstance();
    pConf->GetStringValue(MgConfigProperties::MappingServicePropertiesSection,
                          MgConfigProperties::MappingServicePropertyLegendFont,
                          m_legendFontName,
                          MgConfigProperties::DefaultMappingServicePropertyLegendFont);
    assert(m_legendFontName.length() > 0);
}


MgLegendPlotUtil::~MgLegendPlotUtil()
{
    SAFE_RELEASE(m_svcResource);
}


void MgLegendPlotUtil::AddLegendElement(double dMapScale, Renderer& dr, MgdMap* map, MgPlotSpecification* legendSpec, double legendOffsetX, double legendOffsetY)
{
    if (NULL == map || NULL == legendSpec)
    {
        throw new MgNullArgumentException(
            L"MgLegendPlotUtil.AddLegendElement", __LINE__, __WFILE__, NULL, L"", NULL);
    }

    STRING pageUnits = legendSpec->GetPageSizeUnits();
    double convertUnits = 1.0;
    if (   _wcsicmp(pageUnits.c_str(), L"mm") == 0
        || _wcsicmp(pageUnits.c_str(), L"millimeters") == 0)  // NOXLATE
    {
        convertUnits = IN_TO_MM;
    }
    else if (_wcsicmp(pageUnits.c_str(), L"pixels") == 0)
    {
        convertUnits = dr.GetDpi();
    }

    // Here is where we have to get the layers, rules, etc.
    // But first let's generate a box for the legend border...
    LineBuffer lb(4);
    lb.MoveTo(legendOffsetX                              , legendOffsetY                               );
    lb.LineTo(legendOffsetX                              , legendOffsetY + legendSpec->GetPaperHeight());
    lb.LineTo(legendOffsetX + legendSpec->GetPaperWidth(), legendOffsetY + legendSpec->GetPaperHeight());
    lb.LineTo(legendOffsetX + legendSpec->GetPaperWidth(), legendOffsetY                               );
    lb.Close();

    RS_LineStroke lineStroke;
    dr.ProcessPolyline(&lb, lineStroke);

    //And then do the content.
    BuildLegendContent(map, dMapScale, legendSpec, legendOffsetX, legendOffsetY, dr, convertUnits);
}


void MgLegendPlotUtil::BuildLegendContent(MgdMap* map, double scale, MgPlotSpecification* legendSpec, double legendOffsetX, double legendOffsetY, Renderer& dr, double convertUnits)
{
    RS_TextDef textDef;
    RS_FontDef fontDef(m_legendFontName, legendFontHeightMeters, RS_FontStyle_Regular, RS_Units_Device);
    textDef.font() = fontDef;
    textDef.halign() = RS_HAlignment_Left;
    textDef.valign() = RS_VAlignment_Base;

    // Get the layer info
    double x = legendOffsetX + legendSpec->GetMarginLeft();
    double y = legendOffsetY + legendSpec->GetPaperHeight() - legendSpec->GetMarginTop() - legendFontHeightMeters*M_TO_IN*convertUnits;

    //move one vertical distance down to start with since we reference coordinates
    //for icon and label from the bottom up
    y -= legendSpacing * convertUnits;

    // Add legend entries for layers that do not belong to a group
    ProcessLayersForLegend(map, scale, NULL, x, y, textDef, dr, legendSpec, legendOffsetY, convertUnits);

    // do layer groups
    Ptr<MgLayerGroupCollection> mggroups = map->GetLayerGroups();
    Ptr<MgLayerCollection> layers = map->GetLayers();

    // iterate over groups and draw each group's layers
    for (int k = 0; k < mggroups->GetCount(); k++)
    {
        Ptr<MgLayerGroup> mggroup = mggroups->GetItem(k);

        // Count number of visible layers in this group.
        bool hasVisibleLayers = false;
        for (int l = 0; l < layers->GetCount(); l++)
        {
            Ptr<MgLayerBase> layer = layers->GetItem(l);
            Ptr<MgLayerGroup> layerGroup = layer->GetGroup();
            if ((layer->IsVisible()) && (layerGroup.p == mggroup.p))
            {
                hasVisibleLayers = true;
                break;
            }
        }
        if (!hasVisibleLayers)
            continue;

        if (mggroup == NULL)
        {
            throw new MgNullReferenceException(L"MgLegendPlotUtil.AddLegendElement", __LINE__, __WFILE__, NULL, L"", NULL);
        }
        Ptr<MgLayerGroup> mgparent = mggroup->GetGroup();

        double indent = 0;
        while (mgparent)
        {
            indent += PrintGroupIndent;
            mgparent = mgparent->GetGroup();
        }

        x = legendOffsetX + (defaultLegendMargin + indent)*convertUnits;

        RS_LabelInfo info(x, y + legendTextVertAdjust*convertUnits, textDef);
        dr.ProcessLabelGroup(&info, 1, mggroup->GetLegendLabel(), RS_OverpostType_All, false, NULL, 0.0);

        y -= legendSpacing*convertUnits;

        if (y < legendSpec->GetMarginBottom())
            break;

        // Process the layers
        ProcessLayersForLegend(map, scale, mggroup, x, y, textDef, dr, legendSpec, legendOffsetY, convertUnits);
    }
}


void MgLegendPlotUtil::ProcessLayersForLegend(MgdMap* map, double mapScale, MgLayerGroup* mggroup, double startX, double& startY, RS_TextDef textDef, Renderer& dr, MgPlotSpecification* legendSpec, double legendOffsetY, double convertUnits)
{
    double x;
    double &y = startY;

    //for convenience compute legend bitmap size in plot units (inches, mm, pixels, whatever)
    double dIconWidth = ((double)bitmapPixelWidth / bitmapDpi)*convertUnits;
    double dIconHeight = ((double)bitmapPixelHeight / bitmapDpi)*convertUnits;

    //margin spacing from left edge
    double initialMarginX = defaultLegendMargin * convertUnits;

    //height of every row in the legend
    double verticalDelta = legendSpacing * convertUnits;

    //fudge factor to center text vertically with respect to the legend icon
    double verticalTextAdjust = legendTextVertAdjust * convertUnits;

    //bottom of the legend -- where we stop drawing
    double bottomLimit = legendOffsetY + legendSpec->GetMarginBottom();

    // build the list of layers that need to be processed
    Ptr<MgLayerCollection> layers = map->GetLayers();
    Ptr<MgStringCollection> layerIds = new MgStringCollection();
    for (int i = 0; i < layers->GetCount(); i++)
    {
        Ptr<MgLayerBase> mapLayer = layers->GetItem(i);

        // layer is not currently visible -- don't add to legend
        if (!mapLayer->IsVisible())
            continue;

        Ptr<MgLayerGroup> group = mapLayer->GetGroup();

        bool bRequiredInLegend = false;
        if (group == NULL && mggroup == NULL)
            bRequiredInLegend = true;
        else if (group.p && mggroup && group->GetObjectId() == mggroup->GetObjectId())
            bRequiredInLegend = true;

        if (!bRequiredInLegend)
            continue;

        Ptr<MgResourceIdentifier> layerId = mapLayer->GetLayerDefinition();
        layerIds->Add(layerId->ToString());
    }

    // get resource data
    if (layerIds->GetCount() != 0)
    {
        Ptr<MgStringCollection> layerContents = m_svcResource->GetResourceContents(layerIds, NULL);
        for (int i = 0; i < layerIds->GetCount(); i++)
        {
            for (int j = 0; j < layers->GetCount(); j++)
            {
                Ptr<MgLayerBase> mapLayer = layers->GetItem(j);
                Ptr<MgResourceIdentifier> layerId = mapLayer->GetLayerDefinition();
                if (layerId->ToString() == layerIds->GetItem(i))
                {
                    mapLayer->SetLayerResourceContent(layerContents->GetItem(i));
                    break;
                }
            }
        }
    }

    // process the layers
    for (int i = 0; i < layers->GetCount(); i++)
    {
        Ptr<MgLayerBase> mapLayer = layers->GetItem(i);

        // layer is not currently visible -- don't add to legend
        if (!mapLayer->IsVisible())
            continue;

        Ptr<MgLayerGroup> group = mapLayer->GetGroup();

        bool bRequiredInLegend = false;
        if (group == NULL && mggroup == NULL)
            bRequiredInLegend = true;
        else if (group.p && mggroup && group->GetObjectId() == mggroup->GetObjectId())
            bRequiredInLegend = true;

        if (!bRequiredInLegend)
            continue;

        STRING content = mapLayer->GetLayerResourceContent();
        if (content.empty())
            continue;

        // get layer definition
        auto_ptr<MdfModel::LayerDefinition> ldf(MgLayerBase::GetLayerDefinition(content));

        // Get bitmaps for rules/themes
        MdfModel::VectorLayerDefinition* vl = dynamic_cast<MdfModel::VectorLayerDefinition*>(ldf.get());
        MdfModel::DrawingLayerDefinition* dl = dynamic_cast<MdfModel::DrawingLayerDefinition*>(ldf.get());
        MdfModel::GridLayerDefinition* gl = dynamic_cast<MdfModel::GridLayerDefinition*>(ldf.get());

        if (vl)
        {
            //find correct scale range
            MdfModel::VectorScaleRange* sr = Stylizer::FindScaleRange(*vl->GetScaleRanges(), mapScale);
            if (!sr)
                continue; //layer is not visible due to scale range limits

            MdfModel::FeatureTypeStyleCollection* ftscol = sr->GetFeatureTypeStyles();

            //if there is no feature type style at all, don't add to legend
            if (ftscol->GetCount() == 0)
                continue;

            // if there are multiple feature type styles, using the first one
            MdfModel::FeatureTypeStyle* fts = ftscol->GetAt(0);
            MdfModel::RuleCollection* rules = fts->GetRules();
            int nRuleCount = rules->GetCount();

            //add the layer icon, if any
            x = startX;
            RS_Bounds b2(x, y, x + dIconWidth, y + dIconHeight);

            if (nRuleCount > 1)
            {
                //in case of themed layer, use standard theme icon
                DrawPNG(&dr, (unsigned char*)THEMED_LAYER_ICON, sizeof(THEMED_LAYER_ICON), bitmapPixelWidth, bitmapPixelHeight, b2);
            }
            else
            {
                //otherwise pick the icon from the only rule
                Ptr<MgByteReader> layerIcon = MgMappingUtil::DrawFTS(m_svcResource, fts, bitmapPixelWidth, bitmapPixelHeight, 0);

                if (layerIcon.p)
                {
                    //draw the layer icon
                    MgByteSink sink(layerIcon);
                    Ptr<MgByte> bytes = sink.ToBuffer();

                    //in case of themed layer, use standard theme icon
                    DrawPNG(&dr, bytes->Bytes(), bytes->GetLength(), bitmapPixelWidth, bitmapPixelHeight, b2);
                }
            }

            // Add the layer legend label text.
            x += dIconWidth + initialMarginX;
            RS_LabelInfo info(x, y + verticalTextAdjust, textDef);
            dr.ProcessLabelGroup(&info, 1, mapLayer->GetLegendLabel(), RS_OverpostType_All, false, NULL, 0.0);

            if (nRuleCount > 1)
            {
                //theme icons draw slightly indented
                x = startX + initialMarginX;

                //layer is themed : draw the theme icons under the layer title
                for (int i = 0; i < nRuleCount; i++)
                {
                    MdfModel::Rule* rule = rules->GetAt(i);

                    //move y cursor down one line
                    y -= verticalDelta;

                    if (y < bottomLimit)
                        break;

                    //draw the icon for the current theming rule
                    Ptr<MgByteReader> rdr = MgMappingUtil::DrawFTS(m_svcResource, fts, bitmapPixelWidth, bitmapPixelHeight, i);

                    if (rdr != NULL)
                    {
                        MgByteSink sink(rdr);
                        Ptr<MgByte> bytes = sink.ToBuffer();

                        RS_Bounds b2(x, y, x + dIconWidth, y + dIconHeight);
                        DrawPNG(&dr, bytes->Bytes(), bytes->GetLength(), bitmapPixelWidth, bitmapPixelHeight, b2);
                    }

                    //draw the label after the icon, but also allow
                    //some blank space for better clarity
                    RS_LabelInfo info(x + dIconWidth + initialMarginX, y + verticalTextAdjust, textDef);
                    dr.ProcessLabelGroup(&info, 1, rule->GetLegendLabel(), RS_OverpostType_All, false, NULL, 0.0);
                }
            }
        }
        else if (gl)
        {
            //raster layer

            //find correct scale range
            MdfModel::GridScaleRange* sr = Stylizer::FindScaleRange(*gl->GetScaleRanges(), mapScale);
            if (!sr)
                continue; //layer is not visible due to scale range limits

            //use standard icon
            x = startX;
            RS_Bounds b2(x, y, x + dIconWidth, y + dIconHeight);
            DrawPNG(&dr, (unsigned char*)RASTER_LAYER_ICON, sizeof(RASTER_LAYER_ICON), bitmapPixelWidth, bitmapPixelHeight, b2);

            // Add the layer legend label.
            x += dIconWidth;
            RS_LabelInfo info(x, y + verticalTextAdjust, textDef);
            dr.ProcessLabelGroup(&info, 1, mapLayer->GetLegendLabel(), RS_OverpostType_All, false, NULL, 0.0);
        }
        else if (dl)
        {
            //drawing layer
            if (mapScale < dl->GetMinScale() || mapScale >= dl->GetMaxScale())
                continue;

            //use standard icon
            x = startX;
            RS_Bounds b2(x, y, x + dIconWidth, y + dIconHeight);
            DrawPNG(&dr, (unsigned char*)DWF_LAYER_ICON, sizeof(DWF_LAYER_ICON), bitmapPixelWidth, bitmapPixelHeight, b2);

            // Add the layer legend label.
            x += dIconWidth;
            RS_LabelInfo info(x, y + verticalTextAdjust, textDef);
            dr.ProcessLabelGroup(&info, 1, mapLayer->GetLegendLabel(), RS_OverpostType_All, false, NULL, 0.0);
        }

        //move y cursor down one line
        y -= verticalDelta;

        if (y < bottomLimit)
        {
            break;
        }
    }
}



void MgLegendPlotUtil::ExtentFromMapCenter(MgdMap* map, double metersPerUnit, RS_Bounds& b)
{
    int pixW = map->GetDisplayWidth();
    int pixH = map->GetDisplayHeight();
    int dpi = map->GetDisplayDpi();
    double mapScale = map->GetViewScale();

    // Check to see if we actually have width\height\dpi\scale
    if((0 == pixW) || (0 == pixH) || (0 == dpi) || (0 == mapScale))
    {
        Ptr<MgEnvelope> extents = map->GetMapExtent();
        Ptr<MgCoordinate> ll = extents->GetLowerLeftCoordinate();
        Ptr<MgCoordinate> ur = extents->GetUpperRightCoordinate();
        b = RS_Bounds(ll->GetX(), ll->GetY(), ur->GetX(), ur->GetY());
    }
    else
    {
        double unitsPerPixel = METERS_PER_INCH / (double)dpi / metersPerUnit;

        double mapWidth2 = 0.5 * (double)pixW * unitsPerPixel * mapScale;
        double mapHeight2 = 0.5 * (double)pixH * unitsPerPixel * mapScale;

        //compute map extent that corresponds to pixel extent
        Ptr<MgPoint> center = map->GetViewCenter();
        Ptr<MgCoordinate> thecenter = center->GetCoordinate();

        b = RS_Bounds(thecenter->GetX() - mapWidth2,
                    thecenter->GetY() - mapHeight2,
                    thecenter->GetX() + mapWidth2,
                    thecenter->GetY() + mapHeight2);
    }
}

